/*
 * Copyright (C) 2005-2008  Ben Motmans  <ben.motmans@gmail.com>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using System;
using System.Timers;
using System.Threading;
using System.Collections.Generic;

using Anculus.Core;
using Anculus.Gui;

namespace Galaxium.Core
{
	public static class TimerUtility
	{
		private static List<TimerRequest> _requests;
		private static TimerRequestComparer _comparer;
		
		private static bool _isRunning;
		private static int _timeout;
		private static System.Timers.Timer _timer;
		private static DateTime _timestamp;
		
		private static uint _uid;
		private static object _sync = new object ();
		
		private const double _timeDeviation = 0.1;
		
		static TimerUtility ()
		{
			_requests = new List<TimerRequest> ();
			_comparer = new TimerRequestComparer ();
			
			_timer = new System.Timers.Timer (1000);
			_timer.Elapsed += TimerElapsed;
		}
		
		public static uint RequestInfiniteCallback (VoidDispatchHandler callback, int delay)
		{
			ThrowUtility.ThrowIfNull ("callback", callback);
			ThrowUtility.ThrowIfLessThenOne ("delay", delay);
			
			VoidDispatchContainer container = new VoidDispatchContainer (callback, false);
			return AddCallback (container, delay, true);
		}
		
		public static uint RequestInfiniteCallback (ObjectDispatchHandler callback, object obj, int delay)
		{
			ThrowUtility.ThrowIfNull ("callback", callback);
			ThrowUtility.ThrowIfLessThenOne ("delay", delay);
			
			ObjectDispatchContainer container = new ObjectDispatchContainer (callback, obj, false);
			return AddCallback (container, delay, true);
		}
		
		public static uint RequestCallback (VoidDispatchHandler callback, int delay)
		{
			ThrowUtility.ThrowIfNull ("callback", callback);
			ThrowUtility.ThrowIfLessThenOne ("delay", delay);
			
			VoidDispatchContainer container = new VoidDispatchContainer (callback, false);
			return AddCallback (container, delay, false);
		}
		
		public static uint RequestCallback (ObjectDispatchHandler callback, object obj, int delay)
		{
			ThrowUtility.ThrowIfNull ("callback", callback);
			ThrowUtility.ThrowIfLessThenOne ("delay", delay);
			
			ObjectDispatchContainer container = new ObjectDispatchContainer (callback, obj, false);
			return AddCallback (container, delay, false);
		}
		
		public static void ResetCallback (uint id)
		{
			lock (_sync)
			{
				int index = Sort.BinarySearchIndex<TimerRequest,uint> (_requests, _comparer, id);
				
				if (index >= 0)
					_requests[index].CurrentDelay = 0;
			}
		}
		
		public static void RemoveCallback (uint id)
		{
			lock (_sync)
			{
				int index = Sort.BinarySearchIndex<TimerRequest,uint> (_requests, _comparer, id);
				
				if (index >= 0)
					_requests.RemoveAt (index);
			}
		}
		
		private static uint AddCallback (IDispatchContainer container, int delay, bool inf)
		{
			if (_uid == uint.MaxValue)
				_uid = 0; //this will most likely never occur, since it will take about 250 days with 100 callbacks/second to reach uint.MaxValue
			
			TimerRequest req = new TimerRequest (++_uid, container, delay, inf);
			
			if (_isRunning)
			{
				if (delay < _timeout)
				{
					//add the current waiting time to all requests and reset the timer
					int diff = (int)DateTime.Now.Subtract (_timestamp).TotalMilliseconds;
					
					lock (_sync)
					{
						int len = _requests.Count;
						
						for (int i=0; i<len; i++)
							_requests[i].CurrentDelay += diff;
						
						_requests.Add (req);
					}
					
					ChangeTimerInterval ();
				}
				else
				{
					//add the invers of the current waiting time
					int diff = (int)DateTime.Now.Subtract (_timestamp).TotalMilliseconds;
					req.CurrentDelay = -diff;
					
					lock (_sync)
						_requests.Add (req);
				}
			}
			else
			{
				lock (_sync)
					_requests.Add (req);
				
				_isRunning = true;
				ChangeTimerInterval ();
			}
			
			return _uid;
		}
		
		private static void ChangeTimerInterval ()
		{
			if (_requests.Count == 0)
			{
				_isRunning = false;
				_timer.Enabled = false;
				return;
			}
			
			int delay = _requests[0].RemainingDelay;
			
			for (int i=1; i<_requests.Count; i++)
			{
				if (_requests[i].RemainingDelay < delay)
					delay = _requests[i].RemainingDelay;
			}
			
			Thread.VolatileWrite (ref _timeout, delay);
			
			_timer.Enabled = false;
			_timer.Interval = delay;
			_timestamp = DateTime.Now;
			_timer.Enabled = true;
		}
		
		private static void TimerElapsed (object sender, ElapsedEventArgs args)
		{
			int len = 0;
			List<IDispatchContainer> run = new List<IDispatchContainer> ();
			
			lock (_sync)
			{
				len = _requests.Count;
				List<int> rem = new List<int> ();
				
				for (int i=0; i<len; i++)
				{
					TimerRequest req = _requests[i];
					req.CurrentDelay += _timeout;
					
					if (req.CurrentDelay >= req.Delay)
					{
						if (req.Infinite)
							req.CurrentDelay = 0;
						else
							rem.Add (i);
						
						run.Add (req.Callback);
					}
				}
				
				len = rem.Count;
				
				while (--len >= 0)
					_requests.RemoveAt (rem[len]);
				
				ChangeTimerInterval ();
			}
			
			// Ensure that we invoke the main thread outside the lock
			// Otherwise, if the callback attempts to add/remove a request
			// we'll deadlock
			
			foreach (IDispatchContainer cb in run)
			{
				ThreadUtility.SyncDispatch (new VoidDelegate (delegate
				{
					cb.Execute ();
				}));
			}
		}
		
		private class TimerRequestComparer  : IPropertyComparer<TimerRequest, uint>
		{
			public int Compare (TimerRequest t, uint u)
			{
				return t.Identifier.CompareTo (u);
			}
		}
		
		private class TimerRequest
		{
			public readonly uint Identifier;
			public readonly IDispatchContainer Callback;
			
			public readonly int Delay;
			public int CurrentDelay;
			
			public readonly bool Infinite;
			
			public TimerRequest (uint id, IDispatchContainer callback, int delay, bool infinite)
			{
				this.Identifier = id;
				this.Infinite = infinite;
				this.Delay = delay;
				this.CurrentDelay = 0;
				this.Callback = callback;
			}
			
			public int RemainingDelay
			{
				get { return Delay - CurrentDelay; }
			}
		}
	}
}